package com.springboot.demo.service.impl;

import com.alibaba.druid.sql.visitor.functions.Now;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.springboot.demo.entity.*;
import com.springboot.demo.entity.vo.UserDeviceVO;
import com.springboot.demo.exception.BusinessException;
import com.springboot.demo.mapper.*;
import com.springboot.demo.service.RabbitmqService;
import com.springboot.demo.service.UserService;
import com.springboot.demo.utils.Constant;
import com.springboot.demo.utils.DateUtils;
import com.springboot.demo.utils.RedisUtils;
import com.springboot.demo.utils.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author wubc683
 * @desc
 * @date 2021-11-13 12:04
 */
@Slf4j
@Service
public class RabbitmqServiceImpl implements RabbitmqService {
    @Autowired
    SimpMessagingTemplate simpMessagingTemplate;
    @Autowired
    UserService userService;
    @Autowired
    AlarmMapper alarmMapper;
    @Autowired
    InboxMapper inboxMapper;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    SceneMapper sceneMapper;
    @Autowired
    DeviceMapper deviceMapper;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    TestMapper testMapper;

    private static int num = 0;

    @Override
    public Boolean processMessage(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("code")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Integer code = Integer.valueOf(jsonObject.get("code").toString());
        log.info("msg:{}", msg);
        if (code.equals(Constant.RBTMQCMD.SEND_SCENE_DATA)) {
            return parseSceneData(msg);
        } else if (code.equals(Constant.RBTMQCMD.START_SCENE)) {
            return parseStartScene(msg);
        } else if (code.equals(Constant.RBTMQCMD.STOP_SCENE)) {
            return parseStopScene(msg);
        } else if (code.equals(Constant.RBTMQCMD.DELETE_DEVICE)) {
            return parseDeleteDevice(msg);
        } else if (code.equals(Constant.RBTMQCMD.DEVICE_LOGIN)) {
            return parseDeviceLogin(msg);
        } else if (code.equals(Constant.RBTMQCMD.DEVICE_OFFLINE)) {
            return parseDeviceOffline(msg);
        } else if (code.equals(Constant.RBTMQCMD.DEVICE_CLUSTER_TRANSFER_DEVICE)) {
            return parseDeviceClusterTransferDevice(msg);
        } else if (code == 1) {
//            testRabbit();
            return true;
        } else {
            return false;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    Boolean testRabbit() {
        testMapper.insertNum(num++);
        System.out.println(num);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseDeviceClusterTransferDevice(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("deviceName") && !jsonObject.containsKey("srcDeviceCluster") && !jsonObject.containsKey("destDeviceCluster")
                && !jsonObject.containsKey("deviceId")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        String deviceName = jsonObject.get("deviceName").toString();
        String srcDeviceCluster = jsonObject.get("srcDeviceCluster").toString();
        String destDeviceCluster = jsonObject.get("destDeviceCluster").toString();
        Long micoDeviceId = Long.valueOf(jsonObject.get("deviceId").toString());
        // 检查设备群是否是网页设备群,creatorID为0,说明是网页设备群
        Long srcCreator = deviceMapper.getCreatorByAccount(srcDeviceCluster);
        Long destCreator = deviceMapper.getCreatorByAccount(destDeviceCluster);
        // 获取网页中设备详情
        Device dev = deviceMapper.getDeviceByMicoDeviceId(micoDeviceId);
        // 源设备群是网页设备群
        if (srcCreator == 0) {
            // 获取使用此设备的场景
            List<Scene> sceneByDeviceId = sceneMapper.getSceneByDeviceId(dev.getId());
            // 从我的设备列表删除该设备
            deviceMapper.delMyDeviceByDeviceId(dev.getId());
            // 从场景连接中删除设备
            deviceMapper.updateSceneConnDevice(dev.getId());
            // 通知源设备群用户设备离群
            notifyUserDeviceOffCluster(dev.getId(), srcDeviceCluster);
            // 停止使用此设备的场景
            stopWebScene(sceneByDeviceId);
            // 目的设备群也是网页设备群
            if (destCreator == 0) {
                // 获取目的设备群id
                Long destDevClusterId = deviceMapper.getDeviceIdByName(destDeviceCluster);
                // 修改网页设备的设备群
                deviceMapper.updateWeb2WebDevice(micoDeviceId, destDevClusterId);
                // 通知目的设备群用户设备入群
                notifyUserDevice2Cluster(dev.getId(), destDeviceCluster);
            } else { // 目的设备群不是网页设备群
                // 删除网页设备
                deviceMapper.deleteDeviceById(dev.getId());
            }
        } else { // 源设备群不是网页设备群
            // 获取目的设备群id
            if (destCreator == 0) { // 目的设备群是网页设备群
                Long destDevClusterId = deviceMapper.getDeviceIdByName(destDeviceCluster);
                Device micoDeviceById = deviceMapper.getMicoDeviceById(micoDeviceId);
                // 如果设备是第一次从mico导入网页，则创建新网页设备并关联mico设备,否则修改即可
                if (dev == null) {
                    micoDeviceById.setMicoDeviceId(micoDeviceId);
                    micoDeviceById.setDeviceClusterId(destDevClusterId);
                    micoDeviceById.setDeviceName(deviceName);
                    micoDeviceById.setDeviceClusterName(destDeviceCluster);
                    deviceMapper.saveDeviceFromMico(micoDeviceById);
                    // 通知目的设备群用户设备入群
                    notifyUserDevice2Cluster(micoDeviceById.getId(), destDeviceCluster);
                } else {
                    // 修改网页设备的设备群
                    deviceMapper.updateDeviceFromMico(micoDeviceId, destDevClusterId);
                    if (0 == dev.getDeleteFlag() && !dev.getDeviceClusterId().equals(destDevClusterId)) {
                        // 设备已经在网页设备群，且和需要导入的设备群不同，需要从源网页设备群删除
                        // 获取使用此设备的场景
                        List<Scene> sceneByDeviceId = sceneMapper.getSceneByDeviceId(dev.getId());
                        // 从我的设备列表删除该设备
                        deviceMapper.delMyDeviceByDeviceId(dev.getId());
                        // 从场景连接中删除设备
                        deviceMapper.updateSceneConnDevice(dev.getId());
                        // 通知源设备群用户设备离群
                        notifyUserDeviceOffCluster(dev.getId(), srcDeviceCluster);
                        // 停止使用此设备的场景
                        stopWebScene(sceneByDeviceId);
                        // 通知目的设备群用户设备入群
                        notifyUserDevice2Cluster(dev.getId(), destDeviceCluster);
                    }
                }
            } else { // 目的设备群不是网页设备群
                // 设备已经通过用户名密码导入网页设备群，且目的设备群是源mico设备群，需要从网页设备群删除
                if (dev != null && 0 == dev.getDeleteFlag()) {
                    // 从网页群删除设备
                    deviceMapper.deleteDeviceById(dev.getId());
                    // 获取使用此设备的场景
                    List<Scene> sceneByDeviceId = sceneMapper.getSceneByDeviceId(dev.getId());
                    // 从我的设备列表删除该设备
                    deviceMapper.delMyDeviceByDeviceId(dev.getId());
                    // 从场景连接中删除设备
                    deviceMapper.updateSceneConnDevice(dev.getId());
                    // 通知设备群用户设备离群
                    notifyUserDeviceOffCluster(dev.getId(), srcDeviceCluster);
                    // 停止使用此设备的场景
                    stopWebScene(sceneByDeviceId);
                }
            }
        }
        return true;
    }

    private void stopWebScene(List<Scene> sceneByDeviceId) {
        JSONObject json = new JSONObject();
        // 停止场景运行
        for (Scene s : sceneByDeviceId) {
            json.put("userId", s.getOwnerId());
            json.put("sceneId", s.getId());
            json.put("code", Constant.RBTMQCMD.STOP_SCENE);
            if (s.getState().compareTo(Constant.SceneStatus.RUNNING) < 0) {
                continue;
            }
            rabbitTemplate.convertAndSend(Constant.Rabbitmq.WEB_TO_CLIENT_EXCHANGE, Constant.Rabbitmq.WEB_TO_CLIENT_ROUTING_KEY,
                    json, message -> {
                        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return message;
                    });
            json.clear();
        }
    }

    private void notifyUserDeviceOffCluster(Long deviceId, String srcDeviceCluster) {
        // 获取源设备群用户
        List<User> srcUsers = deviceMapper.getDeviceClusterUserInfoByName(srcDeviceCluster);
        // 通知源设备群该设备离群
        List<Inbox> inboxList = new ArrayList<>();
        for (User u : srcUsers) {
            String content = "设备被移出设备群：" + srcDeviceCluster + ",设备id：" + deviceId;
            Inbox inbox = new Inbox(u.getId(), Constant.MsgType.EVENT, Constant.WEBSOCKETCMD.COMMON_MESSAGE, content);
            inboxList.add(inbox);
        }
        if (!inboxList.isEmpty()) {
            inboxMapper.saveInboxes(inboxList);
        }
        // 给源设备群用户发设备离群消息
        for (int i = 0; i < srcUsers.size(); i++) {
            simpMessagingTemplate.convertAndSendToUser(srcUsers.get(i).getUsername(),
                    Constant.WEBSOCKET_PRIVATE_ROOTINGKEY, inboxList.get(i));
        }
    }

    private void notifyUserDevice2Cluster(Long deviceId, String destDeviceCluster) {
        // 查询设备群用户信息
        List<User> deviceClusterUserInfo = deviceMapper.getDeviceClusterUserInfoByName(destDeviceCluster);
        List<Inbox> inboxList = new ArrayList<>();
        for (User u : deviceClusterUserInfo) {
            String content = "设备被导入设备群：" + destDeviceCluster + ",设备id:" + deviceId;
            Inbox inbox = new Inbox(u.getId(), Constant.MsgType.EVENT, Constant.WEBSOCKETCMD.COMMON_MESSAGE, content);
            inboxList.add(inbox);
        }
        if (!inboxList.isEmpty()) {
            inboxMapper.saveInboxes(inboxList);
        }
        // 给设备群用户发设备入群消息
        for (int i = 0; i < deviceClusterUserInfo.size(); i++) {
            simpMessagingTemplate.convertAndSendToUser(deviceClusterUserInfo.get(i).getUsername(),
                    Constant.WEBSOCKET_PRIVATE_ROOTINGKEY, inboxList.get(i));
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseDeviceLogin(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("deviceId")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Long deviceId = Long.valueOf(jsonObject.get("deviceId").toString());
        Device deviceByMicoDeviceId = deviceMapper.getDeviceByMicoDeviceId(deviceId);
        // 设备未删除，直接修改设备在线标志，如果已删除，则只有直接注册到网页群，设备才在线
        if (deviceByMicoDeviceId != null && deviceByMicoDeviceId.getDeleteFlag() == 0) {
            deviceMapper.updateDeviceOnline(deviceId, Constant.DevStatus.ONLINE);
        } else {
            // 如果mico设备的设备群id和网页设备的设备群id一致，则更新设备登录状态
            deviceMapper.updateDeviceLogin(deviceId, Constant.DevStatus.ONLINE);
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseDeviceOffline(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("deviceId")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Long deviceId = Long.valueOf(jsonObject.get("deviceId").toString());
        // 更新设备登录状态
        deviceMapper.updateDeviceOffline(deviceId, Constant.DevStatus.OFFLINE);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseDeleteDevice(String msg) {
        JSONObject js = JSON.parseObject(msg);
        if (!js.containsKey("deviceIds")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        List<Long> deviceIds = js.getJSONArray("deviceIds").toJavaList(Long.class);

        // 更新设备删除标志
        deviceMapper.updateDevFromDevClusterByIds(deviceIds, Constant.DELETE_FLAG.DELETED);
        // 通知拥有此设备的用户
        List<UserDeviceVO> userDevicesByDeviceIds = deviceMapper.getUserDevicesByDeviceIds(deviceIds);
        Map<Long, List<String>> userIdDevNames = new HashMap<>();
        for (UserDeviceVO u : userDevicesByDeviceIds) {
            List<String> orDefault = userIdDevNames.getOrDefault(u.getUserId(), new ArrayList<>());
            orDefault.add(u.getDeviceName());
            userIdDevNames.put(u.getUserId(), orDefault);
        }
        List<Inbox> inboxList = new ArrayList<>();
        for (Map.Entry<Long, List<String>> entry : userIdDevNames.entrySet()) {
            String content = entry.getValue().toString();
            Inbox inbox = new Inbox(entry.getKey(), Constant.MsgType.EVENT, Constant.WEBSOCKETCMD.COMMON_MESSAGE,
                    content);
            inboxList.add(inbox);
        }
        if (!inboxList.isEmpty()) {
            inboxMapper.saveInboxes(inboxList);
        }
        for (int j = 0; j < inboxList.size(); j++) {
            User userInfoById = userService.getUserInfoById(inboxList.get(j).getOwnerId());
            simpMessagingTemplate.convertAndSendToUser(userInfoById.getUsername(),
                    Constant.WEBSOCKET_PRIVATE_ROOTINGKEY, inboxList.get(j));
        }
        // 从我的设备列表中删除设备
        deviceMapper.delUserDeviceByDevIds(deviceIds);
        // 获取使用此设备的场景
        List<Scene> sceneByDeviceIds = deviceMapper.getSceneByDeviceIds(deviceIds);
        // 删除场景连接中的设备
        sceneMapper.updateSceneConnectDeviceId(deviceIds);
        JSONObject jsonObject = new JSONObject();
        // 停止场景运行
        for (Scene s : sceneByDeviceIds) {
            jsonObject.put("userId", s.getOwnerId());
            jsonObject.put("sceneId", s.getId());
            jsonObject.put("code", Constant.RBTMQCMD.STOP_SCENE);
            if (s.getState().compareTo(Constant.SceneStatus.RUNNING) < 0) {
                continue;
            }
            rabbitTemplate.convertAndSend(Constant.Rabbitmq.WEB_TO_CLIENT_EXCHANGE, Constant.Rabbitmq.WEB_TO_CLIENT_ROUTING_KEY,
                    jsonObject, message -> {
                        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return message;
                    });
            jsonObject.clear();
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseStartScene(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("resp") || !jsonObject.containsKey("sceneId") || !jsonObject.containsKey("userId")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Integer resp = Integer.valueOf(jsonObject.get("resp").toString());
        Long userId = Long.valueOf(jsonObject.get("userId").toString());
        Long sceneId = Long.valueOf(jsonObject.get("sceneId").toString());
        User userInfoById = userService.getUserInfoById(userId);
        String sceneNameById = sceneMapper.getSceneNameById(sceneId);
        String content = "您开启了场景：" + sceneNameById;
        Inbox inbox = new Inbox(userId, Constant.MsgType.EVENT, Constant.WEBSOCKETCMD.COMMON_MESSAGE, content);
        inboxMapper.saveInbox(inbox);
        simpMessagingTemplate.convertAndSendToUser(userInfoById.getUsername().toString(),
                Constant.WEBSOCKET_PRIVATE_ROOTINGKEY, inbox);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseStopScene(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("resp") || !jsonObject.containsKey("userId") || !jsonObject.containsKey("sceneId")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Integer resp = Integer.valueOf(jsonObject.get("resp").toString());
        Long userId = Long.valueOf(jsonObject.get("userId").toString());
        Long sceneId = Long.valueOf(jsonObject.get("sceneId").toString());
        String sceneNameById = sceneMapper.getSceneNameById(sceneId);
        String content = "场景：" + sceneNameById + "被关闭了";
        Inbox inbox = new Inbox(userId, Constant.MsgType.EVENT, Constant.WEBSOCKETCMD.COMMON_MESSAGE, content);
        inboxMapper.saveInbox(inbox);
        User userInfoById = userService.getUserInfoById(userId);
        simpMessagingTemplate.convertAndSendToUser(userInfoById.getUsername(), Constant.WEBSOCKET_PRIVATE_ROOTINGKEY,
                inbox);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean parseSceneData(String msg) {
        JSONObject jsonObject = JSON.parseObject(msg);
        if (!jsonObject.containsKey("userId") || !jsonObject.containsKey("sceneId") || !jsonObject.containsKey(
                "productTime") || !jsonObject.containsKey("var") || !jsonObject.containsKey("alarm")) {
            throw new BusinessException(ResponseResult.JSON_PARSE_ERROR);
        }
        Long userId = Long.valueOf(jsonObject.get("userId").toString());
        Long sceneId = Long.valueOf(jsonObject.get("sceneId").toString());
        Date productTime = null;
        try {
            productTime = DateUtils.parse(jsonObject.get("productTime").toString());
        } catch (ParseException e) {
            throw new BusinessException(ResponseResult.DATE_FORMAT_ERROR);
        }
        String var = jsonObject.get("var").toString();
        String alarm = jsonObject.get("alarm").toString();
        User userInfoById = userService.getUserInfoById(userId);
        simpMessagingTemplate.convertAndSendToUser(userInfoById.getUsername(), Constant.WEBSOCKET_PRIVATE_ROOTINGKEY,
                msg);
        Object obj = redisUtils.hget("history::record", "userId::" + userId);
        int historyRecordCount;
        if (obj == null) {
            historyRecordCount = alarmMapper.getHistoryRecordCount(userId);
            redisUtils.hset("history::record", "userId::" + userId, historyRecordCount);
        } else {
            historyRecordCount = (int) obj;
        }
        // 超过限制条数删除时间最老的1000条记录,1条约100字节
        if (historyRecordCount >= userInfoById.getRecordCount() + 1000) {
            if (userInfoById.getParentId() == 0) {
                alarmMapper.deleteOldDataFromHistoryDataAndAlarm(userInfoById.getId(), 1000);
            } else {
                alarmMapper.deleteOldDataFromHistoryDataAndAlarm(userInfoById.getParentId(), 1000);
            }
        }
        // 变量作为历史记录存入数据库
        JSONArray varArray = JSON.parseArray(var);
        List<HistoryData> dataList = new ArrayList<>();
        for (int i = 0; i < varArray.size(); i++) {
            JSONObject v = varArray.getJSONObject(i);
            HistoryData historyData = new HistoryData();
            Long varId = Long.valueOf(v.get("varId").toString());
            historyData.setVarId(varId);
            Object hget = redisUtils.hget("history::user::" + userId, "sceneId::" + sceneId + "var::" + varId);
            // 场景数据给网页是不间断的，存数据库间隔时间增加,因为基本都是相同数据
            try {
                if (hget != null) {
                    long productTimeTime = productTime.getTime();
                    long lastTime = Long.parseLong(hget.toString());
                    if (productTimeTime - lastTime <= Constant.SAVE_IN_DB_TIME_INTERVAL) {
                        continue;
                    }
                }
            } catch (Exception e) {
                log.error("解析redis历史数据记录中时间错误,userId:{},varId:{}", userId, varId);
                throw new BusinessException(ResponseResult.DATE_FORMAT_ERROR);
            }
            redisUtils.hset("history::user::" + userId, "sceneId::" + sceneId + "var::" + varId, productTime, 600L);
            historyData.setValue(v.get("value").toString());
            historyData.setProductTime(productTime);
            historyData.setSceneId(sceneId);
            historyData.setOwnerId(userId);
            dataList.add(historyData);
        }
        if (!dataList.isEmpty()) {
            alarmMapper.saveSceneHistoryData(dataList);
            Object hget = redisUtils.hget("history::record", "userId::" + userId);
            redisUtils.hset("history::record", "userId::" + userId, (int) hget + dataList.size());
        }
        // 报警作为历史记录存入数据库
        JSONArray alarmArray = JSON.parseArray(alarm);
        List<HistoryAlarm> alarmList = new ArrayList<>();
        for (int i = 0; i < alarmArray.size(); i++) {
            JSONObject v = alarmArray.getJSONObject(i);
            HistoryAlarm historyAlarm = new HistoryAlarm();
            Long varId = Long.valueOf(v.get("varId").toString());
            Integer alarmNo = Integer.valueOf(v.get("alarmNo").toString());
            Integer alarmType = Integer.valueOf(v.get("alarmType").toString());
            Object hget = redisUtils.hget("history::user::" + userId,
                    "sceneId::" + sceneId + "alarmNo::" + alarmNo + "::type::" + alarmType);
            // 场景数据给网页是不间断的，存数据库间隔时间增加,因为基本都是相同数据
            try {
                if (hget != null) {
                    long productTimeTime = productTime.getTime();
                    long lastTime = Long.parseLong(hget.toString());
                    if (productTimeTime - lastTime <= Constant.SAVE_IN_DB_TIME_INTERVAL) {
                        continue;
                    }
                }
            } catch (Exception e) {
                log.error("解析redis历史报警记录中时间错误,userId:{}", userId);
                throw new BusinessException(ResponseResult.DATE_FORMAT_ERROR);
            }
            redisUtils.hset("history::user::" + userId, "sceneId::" + sceneId + "alarmNo::" + alarmNo + "::type::" + alarmType,
                    productTime, 600L);
            historyAlarm.setVarId(varId);
            historyAlarm.setValue(v.get("value").toString());
            historyAlarm.setAlarmNo(alarmNo);
            historyAlarm.setAlarmType(alarmType);
            historyAlarm.setAlarmText(v.get("alarmText").toString());
            historyAlarm.setProductTime(productTime);
            historyAlarm.setSceneId(sceneId);
            historyAlarm.setOwnerId(userId);
            alarmList.add(historyAlarm);
        }
        if (!alarmList.isEmpty()) {
            alarmMapper.saveSceneHistoryAlarm(alarmList);
            Object hget = redisUtils.hget("history::record", "userId::" + userId);
            redisUtils.hset("history::record", "userId::" + userId, (int) hget + alarmList.size());
        }
        return true;
    }
}
